﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TsamIRC
{
    //container for the cmdl parameters
    struct Params
    {
        public bool valid;
        public string host;
        public string nick;
        public int port;
        
    }

    //container for IRC messages
    struct IrcMsg
    {
        public String prefix;
        public String command;
        public String[] parameters;
        public String trailer;

        public String toString()
        {
            String rslt = prefix + " " + command + " ";
            foreach (String s in parameters)
            {
                rslt += s + " ";
            }
            return rslt + " --" + trailer;
        }

        //is buggy, fails when RESPONSE is QUIT or ERROR
        public String toConsole()
        {

            String rslt = "- ";
            if (parameters != null && parameters.Length > 1)
            {
                for (int i = 1; i < parameters.Length; i++)
                {
                    if (parameters[i] != "" && parameters[i] != null)
                    {
                        rslt += parameters[i] + " ";
                    }
                }
            }
            return rslt + trailer;
        }
    }

    class ClientController
    {
        // Member variables.
        private ClientSocket _cSocket;
        private Util _util;
        private Params _p;
        private IrcMsg _latest;
        private Log _log;
        private bool _initSuccess = false;
        private bool _registered = false;
        private bool _connected = false;
        private string _channel = null;
        private DateTime _lastPing;


        /* ClientController */
        public ClientController(string[] args)
        {
            _util = new Util();
            _log = new Log();
            _p = _util.parseCmdl(args); // Parse arguments passed in via command line
        }

        /* Initialize the client. If the parameters are valid, attempt to connect. 
         * Returns _initSuccess if parameters were invalid or connection failed. */
        public bool initialize()
        {
            if (_p.valid)
            {
                _cSocket = new ClientSocket(_p.host, _p.port);
                if ((_initSuccess = _cSocket.initialize()) != true)
                {
                    _log.logLine("Unable to connect to " + _p.host + "... closing program.", true, true);
                }
                else
                {
                    _connected = true;
                }
            }
            else
            {
                _log.logLine("Invalid parameters.\nUsage: IRCClient.exe <server> <nick> <optional:port>", true, true);
            }
            return _initSuccess;
        }

        /* Receive data from server and parse it, then log it. 
         * This function is recvThread's best friend (in the main function). */
        public void receive()
        {
            while (_connected)
            {
                String message = _cSocket.receiveData();
                _latest = _util.parseResponse(message);
                respond(_latest);
            }
        }

        /* This function handles all user input and sends it to the server.
         * Typed messages are also logged and echoed. */
        public void send()
        {
            // Send nickname and user information if we haven't done so already and we know it.
            if (!_registered && _p.nick != "")
                sNick(_p.nick);
            string nick;
            while (_connected) //we scan for user input and enter.
            {
                nick = "<" + _p.nick + "> ";
                Console.Write(nick);   // Optional ?!? (Displays the nick infront of the input)
                String message = Console.ReadLine();
                sendInput(message);
                nick += message;
                _log.logLine(nick, true, true);
            }
        }

        /* Parses the user input and sends the apropriate msg to server
         * Uses private helper functions for each suppported command 
         * All commands must be preceeded with an '/' for command or '@' for privatemsg
         * else they are interpeted as PRIVMSG msg to the active channel */
        private void sendInput(string msg)
        {
            if (msg == "")
                return;
            //here we should probably do some size checking... we may not exceed 512 Bytes. Right??
            if (msg[0] == '/') //we have a command
            {
                string command;
                if (msg.Contains(" "))
                {
                    command = (msg.Substring(1, msg.IndexOf(' ') - 1)).ToUpper();
                    msg = msg.Substring(command.Length + 2);
                }
                else
                {
                    command = (msg.Substring(1)).ToUpper();
                    msg = "";
                }

                switch (command)
                {
                    case "HELP":
                        sHelp();
                        break;
                    case "NICK":
                        sNick(msg);
                        break;
                    case "JOIN":
                        sJoin(msg);
                        break;
                    case "PART":
                        _cSocket.sendData("PART " + _channel + "\r\n");
                        _channel = null;
                        break;
                    case "LIST":
                        sList(msg);
                        break;
                    case "PRIVMSG":
                        sPrivmsg(msg);
                        break;
                    case "QUIT":
                        sQuit(msg);
                        break;
                    case "NAMES":
                        _cSocket.sendData("NAMES " + msg + "\r\n");
                        break;
                    case "VERSION":
                        // Senda version beðni
                        _cSocket.sendData("PRIVMSG " + msg + " :" + (char)001 +"VERSION" + (char)001);
                        break;
                    default:
                        _log.logLine("Unable to parse user input: /" + command, true, true);
                        break;
                }
            }
            else if (msg[0] == '@')     //sending a private message to a specific user/channel, @nick/channel
            {
                string target = (msg.Substring(1, msg.IndexOf(' ') - 1));
                msg = msg.Substring(msg.IndexOf(' ') + 1);
                _cSocket.sendData(":" + _p.nick + " PRIVMSG " + target + " :" + msg + "\r\n");
            }

            else    // a general text message intented for the current channel
            {
                if (_channel != null)
                    _cSocket.sendData("PRIVMSG " + _channel + " :" + msg + "\r\n");
                else
                    _log.WriteOut("Not connected to a channel!");
            }
        }

        /*Looks at the message recieved and sends/takes action if needed
         *Used for responses recieved that need special handling other than writeLine()*/
        private void respond(IrcMsg last)
        {
            if (last.command == "PING")
                _cSocket.sendData("PONG " + _latest.trailer);
            else if (last.command == "QUIT")
            {
                _registered = false;
                _connected = false;
                _log.logLine("Server Acknowlegded / issued a quit command", false, true);
            }
            else if (last.command == "ERROR")
                _log.WriteOut("An error occured: ");
            else if (last.command == "001")
            {
                _registered = true;
                _lastPing = DateTime.Now;
            }
            else if (last.command == "PONG")
                _log.logLine(_latest.toConsole(), true, false);
            else if ((last.command == "PRIVMSG") && (last.trailer == ((char)001 + "VERSION" + (char)001)))
            {
                // Svara version beðni
                int pos = last.prefix.IndexOf('!');
                string incomingnick = last.prefix.Substring(0, pos);
                _cSocket.sendData("NOTICE " + incomingnick + " :" + (char)001 + "VERSION tSAM IRC v0.000001" + (char)001);
            }
            else if (last.command == "PRIVMSG")
            {
                int pos = last.prefix.IndexOf('!');
                string incomingnick = "<";
                incomingnick += last.prefix.Substring(0, pos);
                incomingnick += "> " + _latest.trailer;
                _log.logLine(incomingnick, false, true);
                autoResponse();
            }
            else
            {
                _log.logLine(_latest.toConsole(), false, true);
            }
        }

        /* ----------------------------------------------------
         * autoResponse function
         * Looks at all incoming channel messages and checks for automatic responses
         * ---------------------------------------------------- */
        private void autoResponse()
        {
            if (_channel != null)
            {
                int pos = _latest.prefix.IndexOf('!');
                string incomingnick = _latest.prefix.Substring(0, pos);
                string send;

                string target = _channel;
                if (_latest.parameters[0].Equals(_p.nick))
                {
                    target = incomingnick;
                }

                switch (_latest.trailer)
                {
                    case "Halló":
                        send = "Hæ " + incomingnick;
                        _cSocket.sendData("PRIVMSG " + target + " :" + send);
                        _log.logLine("<" + _p.nick + "> " + send, true, true);
                        break;
                    case "Hvaða dagur er í dag?":
                        DateTime date =  DateTime.Now;
                        send = "Í dag er " + date.ToString("ddd d MMM");
                        _cSocket.sendData("PRIVMSG " + target + " :" + send);
                        _log.logLine("<" + _p.nick + "> " + send, true, true);
                        break;
                    default:

                        send = _latest.trailer;
                        _cSocket.sendData("PRIVMSG " + target + " :" + send);
                        _log.logLine("<" + _p.nick + "> " + send, true, true);
                        break;
                }
                
            }
            else
                _log.WriteOut("Not connected to a channel!");
        }

        private void sHelp()
        {
            _log.WriteOut("/HELP: Lists all commands");
            _log.WriteOut("- /join <#channel>\n- /part (leaves channel)\n- /names <#channel>\n- /nick <nickname>\n- /version\n- /quit <optional message>");
        }

        /*Builds and sends the QUIT message*/
        private void sQuit(string msg)
        {
            if(msg != "")
                _cSocket.sendData("QUIT :" + msg);
            else
                _cSocket.sendData("QUIT :" + msg);
            _log.logLine("Quitting ", true, true);         }

        /*Builds and sends the PRIVMSG message*/
        private void sPrivmsg(string msg)
        {
            throw new NotImplementedException();
        }

        /*Builds and sends the LIST message*/
        private void sList(string msg)
        {
            
        }

        /*Builds and sends the JOIN message*/
        private void sJoin(string msg)
        {
            string[] arr = msg.Split(' ');
            if (arr.Length == 1)
            {
                _channel = arr[0];
                _cSocket.sendData("JOIN " + arr[0]);
            }
            else if (arr.Length == 2)
            {
                _channel = arr[0];
                _cSocket.sendData("JOIN " + arr[0] + " " + arr[1]);
            }
        }

        /*Builds and sends the NICK message*/
        private void sNick(string msg)
        {
            string[] arr = msg.Split(' ');
            if (_registered) //we´re changin our nick-> :WiZ NICK Kilroy         ; WiZ changed his nickname to Kilroy.
            {
                if(arr.Length >= 1){
                    _cSocket.sendData(":" + _p.nick + " NICK " + arr[0]);
                    _p.nick = arr[0];
                }
                else{
                    _log.logLine("Incomplete command. Can not change or set nick.", true, true);
                }
            }
            else { //we have not been registered so this is an init nick + set user
                if (arr.Length >= 1)
                {
                    _p.nick = arr[0];
                    _cSocket.sendData("NICK " + _p.nick);
                    string usr = "USER Mr" + _p.nick + " 0 * :";
                    //there is a second parameter assumed to be the real name
                    if (arr.Length >= 2)
                        usr += arr[1] + "\r\n";
                    else
                        usr += "...\r\n";
                    _cSocket.sendData(usr);
                }
                else
                {
                    _log.logLine("Incomplete command. Can not change or set nick.", true, true);
                }
            }
        }

        /*checks on intervals and sends a PING every 2 minutes
         * if user is registered on server 
         * runs in the interval thread*/
        public void interval()
        {
            while (_connected)
            {
                TimeSpan diff = DateTime.Now.Subtract(_lastPing);
                if (diff.Minutes == 2)
                {
                    if(_registered)
                        _cSocket.sendData("PING :TIMEOUTCHECK");
                    _lastPing = DateTime.Now;
                }
            }
        }

        // Disconnect and clean up.
        public void cleanup()
        {
            _cSocket.closeConnection();
            _log.close();
        }
    }
}
